package com.zjl.redis.old.小项目;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

//@RestController
//@RequestMapping("/redis")
public class RedisTestController {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private Redisson redisson;

    @GetMapping
    public String testRedis(){
//        redisTemplate.opsForValue().set("name","张蛟龙");
//        String name = (String) redisTemplate.opsForValue().get("name");
        return "name";
    }


    //ab -n 1000 -c  100 http://ip地址:8080/redis/Lock
    //像这个接口发送1000个请求   100个并发  linux中
    @GetMapping("Lock")
    public void testLock(){//由于在nginx中 在好几个服务器中运行（好几台主机）  所以java的锁，锁的不是同一个  所以锁几乎无孝
        //获取锁
        String uuid = UUID.randomUUID().toString();
        //uuid  防止这个锁超时了  自动释放  他再次释放时  把锁名一样的锁给释放了
        //但这个程序卡的时候  这个锁超时了  系统自动释放了 当别人访问时  用的锁是同一把锁
        //在别人加锁后   这个卡过来了  要释放锁  由于锁名一样  把别人的锁释放了  导致了锁和没加一样
        String locKey = "lock"+"商品id";

        RLock rLock = redisson.getFairLock(locKey);
        rLock.lock();
        try {
            //添加  业务代码
            //Redisson把你的所有分布式锁 的绝大部分的问题都解决了  除了集群CAP
            //和Lock   用法一样
        }finally {
            /**
             * 如果直接rLock.unlock();解锁
             * 在超高并发的时候   会出现
             * attempt to unlock lock， not locked by current thread by node id :
             *    尝试按节点id解锁当前线程未锁定的锁
             *
             *    Redisson  会自动续命  续锁的超时时间
             */
            if(rLock.isLocked()){//如过他还在锁定状态
                if(rLock.isHeldByCurrentThread()){//是这个线程持有的锁
                    rLock.unlock();//解锁
                }
            }
        }


        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey,uuid,3, TimeUnit.SECONDS);//过期时间

        //获取锁成功，查询num值
        if(lock){
            Object value = redisTemplate.opsForValue().get("num");
            //判断num为空
            if(value==null){
                return;
            }
            int num = Integer.parseInt(value+"");
            redisTemplate.opsForValue().set("num",++num);
            /**
             * 如果业务执行时间 总是大于设置的过期时间，还是无法保证安全问题
             * 如果  系统没宕机  还是希望不要过期，所以要设置缓存续命线程，保证系统没宕机
             * 能执行完
             *
             * redisson//redis分布式锁
             *
             * 确保locKey过期时间大于业务执行时间   缓存续命
             *
             * 集群CAP
             * redis AP
             * redis异步复制造成的锁丢失，
             * redis是主节点执行完就返回是否成功
             *      比如:主节点没来的及把刚刚set进来这条数据给从节点，就挂了。
             *      导致从节点可能没有刚才执行数据
             *
             * Zookeeper：CP
             *    当所有主从节点完成后才返回数据  数据稳定性更好  但是没了高可用  性能降低
             *
             */
            //在finally 里写
            //LUA脚本1(uuid, locKey);//原子锁    LUA脚本最好 官网推荐
            //LUA脚本2(uuid, locKey);
            //事务解决分布式锁(uuid, locKey);

            //Redisson  最好的分布式锁


        }else{
            //其他线程等待
            try {
                Thread.sleep(1000);//睡眠
                testLock();//再调用
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void 事务解决分布式锁(String uuid, String locKey) {
        //释放锁
//        if(uuid.equals(redisTemplate.opsForValue().get("lock"))){
//            //uuid也不能完全成为原子操作  如  但刚判断是 发现uuid一样  正准备释放锁
//            //结果锁过期了  其他应用抢到了锁  结果他还是释放了别人的锁
//            //所以可以用redis的事务
//            redisTemplate.delete("lock");
//        }
        /**
         * 用redis事务解决原子性//redis本身就是原子性的
         */
        while (true){
            redisTemplate.watch(locKey);//监控锁
            if (redisTemplate.opsForValue().get(locKey).equals(uuid)){
                redisTemplate.setEnableTransactionSupport(true);//使redisTemplate支持事务  一定要加
                redisTemplate.multi();//开启事务
                redisTemplate.delete(locKey);//删除锁
                List exec = redisTemplate.exec();//执行事务
                if(exec==null){
                    continue;
                }
            }
            redisTemplate.unwatch();//关闭监视
            break;
        }
    }

    private void LUA脚本2(String uuid, String locKey) {
        try {
            Jedis jedis = RedisUtils.getJedis();
            //定义lua脚本      如果    这个值uuid一样
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then return redis.call('del',KEYS[1]) else return 0 end";
            //                      就删除                      否者返回0  结束
            Object eval = jedis.eval(script, Collections.singletonList(locKey), Collections.singletonList(uuid));
            if("1".equals(eval)){
                System.out.println("成功");
            }else {
                System.out.println("失败");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void LUA脚本1(String uuid, String locKey) {
        /*使用lua脚本来锁  lua脚本支持原子操作*/
        //定义lua脚本      如果    这个值uuid一样
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                "then return redis.call('del',KEYS[1]) else return 0 end";
        //                      就删除                      否者返回0  结束
        //使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        //设置返回值类型 Long
        //因为删除判断的时候，返回的0, 给其封装为数据类型。如果不封装那么默认返回String类型,
        //那么返回字符串与0会有发生错误。
        redisScript.setResultType(Long.class);
        //第一个要是script脚本，第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    }
}
